﻿/*
 * Created by SharpDevelop.
 * User: netcasewqs@gmail.com
 * Date: 2010-9-20
 * Time: 17:58
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using NLite.Internal;
using System.Reflection;
using NLite.Reflection;
using NLite.Log;
using System.Runtime.InteropServices;

namespace NLite.Data
{
	/// <summary>
	/// Description of DbCommandExtensions.
	/// </summary>
	public static class DbCommandExtensions
	{
		/// <summary>
		/// 
		/// </summary>
		/// <param name="cmd"></param>
		/// <returns></returns>
		public static T ExecuteScalar<T>(this IDbCommand cmd)
		{
			Assumes.NotNull<IDbCommand>(cmd);
			
			var from = cmd.ExecuteScalar();
			return (T)Mapper.Map(from,from.GetType(),typeof(T));
		}
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="cmd"></param>
		/// <returns></returns>
		public static IEnumerable<T> ExecuteList<T>(this IDbCommand cmd) where T:new()
		{
			Assumes.NotNull<IDbCommand>(cmd);
            return cmd.ExecuteReader().ToList<T>();
		}
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="cmd"></param>
		/// <param name="parameterName"></param>
		/// <param name="parameterValue"></param>
		/// <returns></returns>
		public static IDbDataParameter CreateParameter(this IDbCommand cmd, string parameterName,object parameterValue)
		{
			var parameter = cmd.CreateParameter();
			parameter.ParameterName = parameterName;
			parameter.Value = parameterValue;
			return parameter;
		}
		
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="cmd"></param>
		/// <returns></returns>
		public static IEnumerable<T> ExecuteScalarList<T>(this IDbCommand cmd)
		{
            var items = new List<T>();

            using (var p = cmd.ExecuteReader())
            {
                while (p.Read())
                {
                    var from = p.GetValue(0);
                    items.Add( (T)Mapper.Map(from, from.GetType(), typeof(T)));
                }
            }

            return items;
		}
	}
	
    /// <summary>
    /// 
    /// </summary>
	public static class DbConnectionExtensions
	{
        static readonly ILog log = LogManager.GetLogger(typeof(DbConnectionExtensions));
		/// <summary>
		/// 
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="query"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static IDbCommand CreateCommand(this IDbConnection conn,string query,params object[] parameters)
		{
			return CreateCommand(conn,null,query,parameters);
		}
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="query"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static IDbCommand CreateCommand(this IDbConnection conn,Action<IDbCommand> callback, string query,params object[] parameters)
		{
			var cmd = conn.CreateCommand();
			cmd.CommandText = query;
            log.Debug(query);

            foreach (var p in parameters.Select((v, i) => cmd.CreateParameter("p" + i, v = v == null ? DBNull.Value : v)))
				cmd.Parameters.Add(p);
			if (conn.State != ConnectionState.Open)
				conn.Open();
			
			if(callback != null)
				callback(cmd);
			
			return cmd;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="query"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static T ExecuteScalar<T>(this IDbConnection conn, string query, params object[] parameters)
		{
			using(var cmd = conn.CreateCommand(query,parameters))
				return cmd.ExecuteScalar<T>();
		}
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="query"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static IEnumerable<T> ExecuteScalarList<T>(this IDbConnection conn, string query, params object[] parameters)
		{
			using(var cmd = conn.CreateCommand(query,parameters))
				return cmd.ExecuteScalarList<T>();
		}
		
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="query"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static IEnumerable<T> ExecuteList<T>(this IDbConnection conn, string query, params object[] parameters)
			where T : new()
		{
			using(var cmd = conn.CreateCommand(query,parameters))
				return cmd.ExecuteList<T>();
		}
		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="conn"></param>
		/// <param name="query"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static int ExecuteNonQuery(this IDbConnection conn, string query, params object[] parameters)
		{
			using(var cmd = conn.CreateCommand(query,parameters))
				return cmd.ExecuteNonQuery();
		}
		
	

        /// <summary>
        /// 
        /// </summary>
        /// <param name="conn"></param>
        /// <param name="query"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static IDataReader ExecuteReader(this IDbConnection conn, string query, params object[] parameters)
        {
            using (var cmd = conn.CreateCommand(query, parameters))
                return cmd.ExecuteReader();
        }
	}

    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class DAOException : NLiteException
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        public DAOException(string p)
            : base(p)
        {
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        /// <param name="e"></param>
        public DAOException(string p, Exception e)
            : base(p, e)
        {
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public static class DataReaderExtensions
    {
        private static readonly BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static DataTable ToDataTable(this IDataReader reader)
        {
            var tb = new DataTable("Table1");
            var fieldCount = reader.FieldCount;
            for (int i = 0; i < fieldCount; i++)
                tb.Columns.Add(reader.GetName(i).Replace("_",""), reader.GetFieldType(i));

            using (reader)
            {
                while (reader.Read())
                {
                    var row = new object[fieldCount];
                    reader.GetValues(row);
                    tb.Rows.Add(row);
                }
            }

            return tb;
        }

        struct Mapping
        {
            public Type SourceType;
            public Type TargetType;

            public Action<object,object> SetMember;

        }

        private static Dictionary<int, Mapping> CreateMappings(IDataReader dataRecord, Type elementType)
        {
            var mappings = new Dictionary<int, Mapping>();
            var fieldCount = dataRecord.FieldCount;

            for (int i = 0; i < fieldCount; i++)
            {
                var columnName = dataRecord.GetName(i);
                var mapping = new Mapping { SourceType = dataRecord.GetFieldType(i) };

                var member = elementType.GetMember(columnName, flags).FirstOrDefault();
                if (member != null)
                {
                    mapping.SetMember = member.GetSetter<object, object>();
                    mapping.TargetType = member.GetMemberType();
                    mappings.Add(i, mapping);
                }
            }
            return mappings;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="p"></param>
        /// <returns></returns>
        public static ICollection<TEntity> ToList<TEntity>(this IDataReader reader) where TEntity:new()
        {
            var    items = new HashSet<TEntity>();
            var mappings = CreateMappings(reader, typeof(TEntity));

            while (reader.Read())
            {
                var item =new TEntity();

                foreach (var i in mappings.Keys)
                {
                    if (reader.IsDBNull(i))
                        continue;

                    var m = mappings[i];
                    var value = Mapper.Map(reader[i], m.SourceType, m.TargetType);

                    m.SetMember(item, value);
                }

                items.Add(item);
            }

            return items;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="p"></param>
        /// <returns></returns>
        public static ICollection<TEntity> ToList<TEntity>(this IDataReader reader,Func<IDataReader,TEntity> creator) 
        {
            var items = new HashSet<TEntity>();

            if (creator == null)
                return items;

            while (reader.Read())
            {
                var item = creator(reader);
                if(item != null)
                    items.Add(item);
            }

            return items;
        }

        class OfflineDataReader : IDataReader
        {
            private ResultSet[] results;

            private bool isClosed = false;
            private int currentRowIndex = -1;
            private int currentResultIndex = 0;

            private byte[] cachedByteArray;
            private char[] cachedCharArray;
            private int cachedColIndex = -1;

            public OfflineDataReader(IDataReader reader)
            {
                var resultList = new List<ResultSet>(2);

                try
                {
                    currentRowIndex = 0;
                    resultList.Add(new ResultSet(reader, true));

                    while (reader.NextResult())
                        resultList.Add(new ResultSet(reader, false));

                    results = resultList.ToArray();
                }
                catch (Exception e)
                {
                    throw new DAOException("There was a problem converting an IDataReader to NDataReader", e);
                }
                finally
                {
                    reader.Close();
                }
            }


            private void ClearCache()
            {
                cachedByteArray = null;
                cachedCharArray = null;

                cachedColIndex = -1;
            }

            private ResultSet GetCurrentResult()
            {
                return results[currentResultIndex];
            }

            private object GetValue(string name)
            {
                return GetCurrentResult().GetValue(currentRowIndex, name);
            }

            #region IDataReader Members

            public int RecordsAffected
            {
                get { throw new NotImplementedException("NDataReader should only be used for SELECT statements!"); }
            }

            /// <summary></summary>
            public bool IsClosed
            {
                get { return isClosed; }
            }

            public bool NextResult()
            {
                currentResultIndex++;
                currentRowIndex = -1;

                if (currentResultIndex >= results.Length)
                {
                    // move it back to the last result
                    currentResultIndex--;
                    return false;
                }
                ClearCache();

                return true;
            }

            public void Close()
            {
                isClosed = true;
            }

            public bool Read()
            {
                currentRowIndex++;

                if (currentRowIndex >= results[currentResultIndex].RowCount)
                {
                    currentRowIndex--;
                    return false;
                }

                ClearCache();

                return true;
            }

            public int Depth
            {
                get { return currentResultIndex; }
            }

            public DataTable GetSchemaTable()
            {
                return GetCurrentResult().GetSchemaTable();
            }

            #endregion

            #region IDisposable Members
            public void Dispose()
            {
                isClosed = true;
                ClearCache();
                results = null;
            }

            #endregion

            #region IDataRecord Members

           
            public int GetInt32(int i)
            {
                return Convert.ToInt32(GetValue(i));
            }

            public object this[string name]
            {
                get { return GetValue(name); }
            }

            public object this[int i]
            {
                get { return GetValue(i); }
            }

            public object GetValue(int i)
            {
                return GetCurrentResult().GetValue(currentRowIndex, i);
            }

            public bool IsDBNull(int i)
            {
                return GetValue(i).Equals(DBNull.Value);
            }

          
            public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferOffset, int length)
            {
                if (cachedByteArray == null || cachedColIndex != i)
                {
                    cachedColIndex = i;
                    cachedByteArray = (byte[])GetValue(i);
                }

                long remainingLength = cachedByteArray.Length - fieldOffset;

                if (buffer == null)
                {
                    return remainingLength;
                }

                if (remainingLength < length)
                {
                    length = (int)remainingLength;
                }

                Array.Copy(cachedByteArray, fieldOffset, buffer, bufferOffset, length);

                return length;
            }

            public byte GetByte(int i)
            {
                return Convert.ToByte(GetValue(i));
            }

            public System.Type GetFieldType(int i)
            {
                return GetCurrentResult().GetFieldType(i);
            }

            public decimal GetDecimal(int i)
            {
                return Convert.ToDecimal(GetValue(i));
            }

            public int GetValues(object[] values)
            {
                return GetCurrentResult().GetValues(currentRowIndex, values);
            }

            public string GetName(int i)
            {
                return GetCurrentResult().GetName(i);
            }

            public int FieldCount
            {
                get { return GetCurrentResult().GetFieldCount(); }
            }

            public long GetInt64(int i)
            {
                return Convert.ToInt64(GetValue(i));
            }

            public double GetDouble(int i)
            {
                return Convert.ToDouble(GetValue(i));
            }

            public bool GetBoolean(int i)
            {
                return Convert.ToBoolean(GetValue(i));
            }

            public Guid GetGuid(int i)
            {
                return (Guid)GetValue(i);
            }

            public DateTime GetDateTime(int i)
            {
                return Convert.ToDateTime(GetValue(i));
            }

            public int GetOrdinal(string name)
            {
                return GetCurrentResult().GetOrdinal(name);
            }

            public string GetDataTypeName(int i)
            {
                return GetCurrentResult().GetDataTypeName(i);
            }

            public float GetFloat(int i)
            {
                return Convert.ToSingle(GetValue(i));
            }

            public IDataReader GetData(int i)
            {
                throw new NotImplementedException("GetData(int) has not been implemented.");
            }

            public long GetChars(int i, long fieldOffset, char[] buffer, int bufferOffset, int length)
            {
                if (cachedCharArray == null || cachedColIndex != i)
                {
                    cachedColIndex = i;
                    cachedCharArray = (char[])GetValue(i);
                }

                long remainingLength = cachedCharArray.Length - fieldOffset;

                if (remainingLength < length)
                {
                    length = (int)remainingLength;
                }

                Array.Copy(cachedCharArray, fieldOffset, buffer, bufferOffset, length);

                return length;
            }

            public string GetString(int i)
            {
                return Convert.ToString(GetValue(i));
            }

            public char GetChar(int i)
            {
                return Convert.ToChar(GetValue(i));
            }

            public short GetInt16(int i)
            {
                return Convert.ToInt16(GetValue(i));
            }

            #endregion

            [Serializable]
            private class ResultSet
            {
                private readonly object[][] records;
                private int colCount = 0;

                [NonSerialized]
                private readonly DataTable schemaTable;

                private readonly IDictionary<string, int> fieldNameToIndex = new Dictionary<string, int>();
                private readonly IList<string> fieldIndexToName = new List<string>();
                private readonly IList<System.Type> fieldTypes = new List<System.Type>();
                private readonly IList<string> fieldDataTypeNames = new List<string>();

                internal ResultSet(IDataReader reader, bool isOfflineReader)
                {
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        string fieldName = reader.GetName(i);
                        fieldNameToIndex[fieldName] = i;
                        fieldIndexToName.Add(fieldName);
                        fieldTypes.Add(reader.GetFieldType(i));
                        fieldDataTypeNames.Add(reader.GetDataTypeName(i));
                    }

                    colCount = reader.FieldCount;

                    schemaTable = reader.GetSchemaTable();

                    List<object[]> recordsList = new List<object[]>();
                    int rowIndex = 0;

                    while (isOfflineReader || reader.Read())
                    {
                        rowIndex++;

                        object[] colValues = new object[reader.FieldCount];
                        reader.GetValues(colValues);
                        recordsList.Add(colValues);

                        // we can go back to reading a p like normal and don't need
                        // to consider where we started from.
                        isOfflineReader = false;
                    }

                    records = recordsList.ToArray();
                }

                /// <summary>
                /// 
                /// </summary>
                /// <param name="colIndex"></param>
                /// <returns></returns>
                public string GetDataTypeName(int colIndex)
                {
                    return fieldDataTypeNames[colIndex];
                }

                /// <summary>
                /// 
                /// </summary>
                /// <returns></returns>
                public int GetFieldCount()
                {
                    return fieldIndexToName.Count;
                }

                /// <summary>
                /// 
                /// </summary>
                /// <param name="colIndex"></param>
                /// <returns></returns>
                public System.Type GetFieldType(int colIndex)
                {
                    return fieldTypes[colIndex];
                }

                /// <summary>
                /// 
                /// </summary>
                /// <param name="colIndex"></param>
                /// <returns></returns>
                public string GetName(int colIndex)
                {
                    return fieldIndexToName[colIndex];
                }

                /// <summary></summary>
                public DataTable GetSchemaTable()
                {
                    return schemaTable;
                }

                /// <summary>
                /// 
                /// </summary>
                /// <param name="colName"></param>
                /// <returns></returns>
                public int GetOrdinal(string colName)
                {
                    // Martijn Boland, 20041106: perform a case-sensitive search first and if that returns
                    // null, perform a case-insensitive search (as being described in the IDataRecord 
                    // interface, see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemDataIDataRecordClassItemTopic1.asp.
                    // This is necessary for databases that don't preserve the case of field names when
                    // they are created without quotes (e.g. DB2, PostgreSQL).
                    if (fieldNameToIndex.ContainsKey(colName))
                    {
                        return fieldNameToIndex[colName];
                    }

                    foreach (KeyValuePair<string, int> pair in fieldNameToIndex)
                    {
                        if (string.Equals(pair.Key, colName, StringComparison.InvariantCultureIgnoreCase))
                        {
                            return pair.Value;
                        }
                    }

                    throw new IndexOutOfRangeException(String.Format("No column with the specified name was found: {0}.", colName));
                }

                /// <summary>
                /// 
                /// </summary>
                /// <param name="rowIndex"></param>
                /// <param name="colIndex"></param>
                /// <returns></returns>
                public object GetValue(int rowIndex, int colIndex)
                {
                    return records[rowIndex][colIndex];
                }

                /// <summary>
                /// 
                /// </summary>
                /// <param name="rowIndex"></param>
                /// <param name="colName"></param>
                /// <returns></returns>
                public object GetValue(int rowIndex, string colName)
                {
                    return GetValue(rowIndex, GetOrdinal(colName));
                }

                /// <summary>
                /// 
                /// </summary>
                /// <param name="rowIndex"></param>
                /// <param name="values"></param>
                /// <returns></returns>
                public int GetValues(int rowIndex, object[] values)
                {
                    Array.Copy(records[rowIndex], 0, values, 0, colCount);
                    return colCount;
                }

                /// <summary></summary>
                public int RowCount
                {
                    get { return records.Length; }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public static IDataReader ToOfflineDataReader(this IDataReader reader)
        {
            return new OfflineDataReader(reader);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public static class DataTableExtensions
    {
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="tb"></param>
        /// <returns></returns>
        public static ICollection<TEntity> ToList<TEntity>(this DataTable tb) where TEntity : new()
        {
            return DataReaderExtensions.ToList<TEntity>(tb.CreateDataReader());
        }
    }
}
